package com.miyake.dsp.analyzer;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.miyake.dsp.ComplexArray;
import com.miyake.dsp.Processor;
import com.miyake.dsp.ScilabExecute;
import com.miyake.dsp.common.UnitConverter;

public class PeakSearch implements Processor {
	private ComplexArray source;
	private PeakSearchSetting setting;
	
	public PeakSearch(PeakSearchSetting setting) {
		this.setting = setting;
	}
	
	@Override
	public void setSource(ComplexArray source) {
		this.source = source;
	}

	@Override
	public ComplexArray getSource() {
		return this.source;
	}

	class Peak {
		class Data {
			int index;
			double level;
		}
		
		Data peak;
		Data left;
		Data right;
	}
	enum EdgeType {
		Peak, Dip
	};
	private ComplexArray getPeaks() {
		double[] in = source.getRealPart();
		double[] diff = ScilabExecute.execute("ret=diff(data1)", source, "ret").getRealPart();
		int up = 0, down = 0;

		
		class PeakDip {

			int index;
			double level;
			EdgeType type;
			public PeakDip(int index, double level, EdgeType type) {
				super();
				this.index = index;
				this.level = level;
				this.type = type;
			}
		}
		List<PeakDip> peakDips = new ArrayList<PeakDip>();

		double max = Double.MIN_VALUE;

		for (int i = 0; i < diff.length; i++) {
			if (diff[i] >= 0) { // Going up
				up++; // Now going up
				if (down > 0) { // Just come from Down
					peakDips.add(new PeakDip(i, in[i], EdgeType.Dip)); // It was dip
				}
				down = 0; // Cancel down flag
			}
			else if (diff[i] < 0) { // Going down
				down++; // Now going down
				if (up > 0) { // Just come from Up
					peakDips.add(new PeakDip(i, in[i], EdgeType.Peak)); // It was up
				}
				up = 0; // cancel up flag
			}
			
			if (max < in[i]) {
				max = in[i];
			}
		}		
		
		PeakDip[] arrPeakDip = peakDips.toArray(new PeakDip[0]);
		
		double thr = UnitConverter.toLinear(setting.getThreshold());
		double sli = UnitConverter.toLinear(setting.getSlice());
		
		List<Integer> peaks = new ArrayList<Integer>();
		double dip;
		double lvl;
		for (int i = 1; i < arrPeakDip.length-1; i++) {
			if (arrPeakDip[i].type.equals(EdgeType.Peak) && (max / arrPeakDip[i].level) < sli ) {
				dip = Math.max(arrPeakDip[i-1].level, arrPeakDip[i+1].level);
				lvl = arrPeakDip[i].level;
				if (lvl / dip > thr) {
					peaks.add(arrPeakDip[i].index);
				}
			}
		}
		
		double[] ret = new double[peaks.size()];
		int i = 0;
		for (Integer p : peaks) {
			ret[i++] = p;
		}
		return new ComplexArray(ret);
	}
	@Override
	public ComplexArray getProcessed() {
		int ii = 0;
		if (ii == 0) {
			return getPeaks();
		}

		List<Integer> peaks = new LinkedList<Integer>();
		double[] in = source.getRealPart();
		double[] diff = ScilabExecute.execute("ret=diff(data1)", source, "ret").getRealPart();
		int up = 0, down = 0;
		double bottom = in[0];
		double thr = UnitConverter.toLinear(setting.getThreshold());
		double sli = UnitConverter.toLinear(setting.getSlice());
		double max = getMax(in);
		for (int i = 0; i < diff.length; i++) {
			if (diff[i] >= 0) {
				up++;
				if (down > 0 && in[i] != 0.0) {
					bottom = in[i];
					bottom = round(bottom);
				}
				down = 0;
			}
			else if (diff[i] < 0) {
				down++;
				if (up > 2) {
					if (in[i] / bottom > thr && (max / in[i]) < sli ) {
						peaks.add(i);
					}
				}
				up = 0;
			}
		}
		double[] ret = new double[peaks.size()];
		int i = 0;
		for (Integer p : peaks) {
			ret[i++] = p;
		}
		return new ComplexArray(ret);
	}


	private double round(double bottom) {
		if (bottom < 1e-15) {
			return 1e-15;
		}
		return bottom;
	}

	private double getMax(double[] in) {
		double max = Double.MIN_VALUE;
		for (int i = 0; i < in.length; i++) {
			if (max < in[i]) {
				max = in[i];
			}
		}
		return max;
	}

	@Override
	public Object getSetting() {
		return setting;
	}
	
	public int getPeakIndex() {
		double[] d = this.source.getRealPart();
		double max = Double.MIN_VALUE;
		int index = 0;
		for (int i = 0; i < d.length; i++) {
			if (d[i] > max) {
				max = d[i];
				index = i;
			}
		}
		return index;
	}
}
